﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Utilities;
using System.Drawing.Drawing2D;
using Forms;
using System.Diagnostics;

namespace Utilities
{
    [Designer(typeof(SmartTagDesigner))]
    [ToolboxBitmap(typeof(System.Windows.Forms.ContextMenuStrip))]
    public class ContextMenuStrip : Component
    {
        #region 公共成员
        /// <summary>
        /// 文本保留宽度
        /// </summary>
        [Description("文本保留宽度(除文本长度以外多占用的长度，用来调整菜单宽度)")]
        public int TextWidthSp { get; set; }
        /// <summary>
        /// 文本保留高度
        /// </summary>
        [Description("文本保留高度(除文本字体高度以外多占用的高度，用来调整菜单项高度)")]
        public int TextHeightSp { get; set; }
        /// <summary>
        /// 快捷键保留宽度
        /// </summary>
        [Description("快捷键文本保留宽度(除文本长度以外多占用的宽度，用来调整菜单宽度)")]
        public int QuickKeyWidthSp { get; set; }
        /// <summary>
        /// 图片所占宽度
        /// </summary>
        [Description("菜单项图标占用宽度，和图标大小无关")]
        public int ImageWidth { get; set; }
        /// <summary>
        /// 分割线高度
        /// </summary>
        [Description("分割线所占高度")]
        public int SplitHeight { get; set; }
        /// <summary>
        /// 菜单总宽度
        /// </summary>
        [Description("是否显示边框"), DefaultValue(false)]
        public bool ShowBorder { get; set; }
        [Description("是否显示阴影"), DefaultValue(true)]
        public bool ShowShadow { get; set; }
        [Description("阴影范围")]
        public int ShadowSize { get; set; }
        /// <summary>
        /// 菜单集合
        /// </summary>
        [Description("菜单项集合"),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public MenuItemCollection Items
        {
            get;
            private set;
        }
        /// <summary>
        /// 鼠标经过颜色
        /// </summary>
        [Description("鼠标经过颜色")]
        public Color HoverColor { get; set; }
        /// <summary>
        /// 边框颜色
        /// </summary>
        [Description("边框颜色")]
        public Color BoderColor { get; set; }
        /// <summary>
        /// 多级菜单箭头颜色
        /// </summary>
        [Description("小箭头颜色")]
        public Color ArrowColor { get; set; }
        /// <summary>
        /// 多级菜单鼠标经过箭头颜色
        /// </summary>
        [Description("小箭头选中颜色")]
        public Color ArrowHoverColor { get; set; }
        [Description("无效化时字体颜色")]
        public Color DisableColor { get; set; }
        [TypeConverter(typeof(ExpandableObjectConverter)),
        Description("展开以编辑文字排列方式")]
        public StringFormat Format { get; set; }
        [Description("勾的颜色")]
        public Color MarkColor { get; set; }
        [Description("字体")]
        public Font Font
        {
            get
            {
                if (font == null)
                    font = new Font("微软雅黑", 9);
                return font;
            }
            set { font = value; }
        }
        [Description("背景色")]
        public Color BackColor { get; set; }
        [Description("字体颜色")]
        public Color ForeColor { get; set; }
        [Description("菜单图标大小,图片不等于此大小会自动拉伸或收缩")]
        public Size ImageSize { get; set; }
        [TypeConverter(typeof(StringConverter)),
        Description("与对象关联的用户自定义数据")]
        public object Tag { get; set; }
        /// <summary>
        /// 鼠标点击事件
        /// </summary>
        [Description("鼠标单击事件")]
        public event Action<TextItem> ItemClicked;
        [Description("Check改变事件")]
        public event Action<TextItem> CheckChanged;
        #endregion

        private GdiForm gdiForm;
        BaseMenuItem m_CurrentItem;
        /// <summary>
        /// 当前选中菜单
        /// </summary>
        MenuItemCollection m_HoverMenuItems;
        Font font;
        /// <summary>
        /// 扩展长度
        /// </summary>
        const int expendLength = 1;
        const int shadowAlpha = 60;
        Color shadeColor = Color.FromArgb(shadowAlpha, Color.Black);
            
        public ContextMenuStrip()
        {
            Items = new MenuItemCollection(null);
            Items.MenuStrip = this;
            ShadowSize = 7;
            TextWidthSp = 30;
            TextHeightSp = 10;
            QuickKeyWidthSp = 10;
            ImageWidth = 32;
            SplitHeight = 3;
            ForeColor = Color.FromArgb(40, 40, 40);
            MarkColor = Color.FromArgb(60, 60, 60);
            BackColor = Color.FromArgb(240, 240, 240);
            HoverColor = Color.FromArgb(220, 220, 220);
            BoderColor = Color.FromArgb(201, 201, 201);
            ArrowColor = Color.FromArgb(153, 153, 153);
            DisableColor = Color.FromArgb(109, 109, 109);
            ArrowHoverColor = Color.FromArgb(20, 132, 244);
            ImageSize = new Size(16, 16);
            Format = new StringFormat();
            Format.Alignment = StringAlignment.Near;
            Format.LineAlignment = StringAlignment.Center;
            ShowShadow = true;
            gdiForm = new GdiForm();
            gdiForm.ShowInTaskbar = false;
            //gdiForm.UseMove = false;
            gdiForm.Draw += gidForm_Draw;
            gdiForm.MouseMove += gdiForm_MouseMove;
            gdiForm.MouseUp += gdiForm_MouseUp;
            gdiForm.LostFocus += gdiForm_LostFocus;
            gdiForm.WindowState = FormWindowState.Maximized;
            gdiForm.Refresh();
        }

        public void Hide()
        {
            gdiForm.Hide();
        }

        private void gidForm_Draw(object sender, PaintEventArgs e)
        {
            DrawItems(e.Graphics, Items);
        }

        /// <summary>
        /// 非递归画菜单项
        /// </summary>
        //private void DrawItems(Graphics g, MenuItemCollection items)
        //{
        //    var queue = new Queue<MenuItemCollection>();
        //    queue.Enqueue(items);
        //    while (queue.Count > 0)
        //    {
        //        var menuItems = queue.Dequeue();
        //        DrawBackGround(g, menuItems);
        //        foreach (BaseMenuItem item in menuItems)
        //        {
        //            var x = new TextItem();
        //            item.Draw(g, this, ref x);
        //            //如果被选中
        //            if (item.Selected && item is TextItem)
        //            {
        //                var txtItem = item as TextItem;
        //                //并且含有子菜单，将该项入队
        //                if (txtItem.Items.Count > 0)
        //                {
        //                    queue.Enqueue(txtItem.Items);
        //                }
        //            }
        //        }
        //    }
        //}

        /// <summary>
        /// 递归画菜单项
        /// </summary>
        /// <param name="g"></param>
        /// <param name="items"></param>
        internal void DrawItems(Graphics g, MenuItemCollection items)
        {
            DrawBackGround(g, items);
            TextItem dropItem = null;
            foreach (BaseMenuItem item in items)
            {
                item.Draw(g, this, ref dropItem);
            }
            if (dropItem != null)
            {
                DrawItems(g, dropItem.Items);
            }
        }

        public void DrawBackGround(Graphics g, MenuItemCollection items)
        {
            if (items.Bounds.IsEmpty) return;
            Rectangle rect = items.Bounds;
            DrawShadow(g, rect, ShadowSize);
            using (SolidBrush brush = new SolidBrush(BackColor))
                g.FillRectangle(brush, rect);
            rect.Width -= 1; rect.Height -= 1;
            if (ShowBorder)
            {
                using (Pen pen = new Pen(BoderColor))
                    g.DrawRectangle(pen, rect);
            }
        }

        private void DrawShadow(Graphics g, Rectangle rect, int radius)
        {
            if (!ShowShadow) return;
            Color[] colors1 = new Color[] { Color.Transparent, Color.FromArgb(21, shadeColor), shadeColor };
            ColorBlend blend1 = new ColorBlend();
            blend1.Positions = new float[] { 0, 0.61f, 1 };
            blend1.Colors = colors1;

            Color[] colors2 = new Color[] { shadeColor, Color.FromArgb(21, shadeColor), Color.Transparent };
            ColorBlend blend2 = new ColorBlend();
            blend2.Positions = new float[] { 0, 0.39f, 1 };
            blend2.Colors = colors2;

            int length = radius * 2;
            Rectangle rectTop = new Rectangle(rect.X, rect.Y - radius, rect.Width, radius);
            using (LinearGradientBrush brush = new LinearGradientBrush(rectTop, Color.Transparent, shadeColor, LinearGradientMode.Vertical))
            {
                brush.InterpolationColors = blend1;
                g.FillRectangle(brush, rectTop);
            }
            Rectangle rectLeft = new Rectangle(rect.X - radius, rect.Y, radius, rect.Height);
            using (LinearGradientBrush brush = new LinearGradientBrush(rectLeft, Color.Transparent, shadeColor, LinearGradientMode.Horizontal))
            {
                brush.InterpolationColors = blend1;
                g.FillRectangle(brush, rectLeft);
            }
            Rectangle rectRight = new Rectangle(rect.Right , rect.Y, radius, rect.Height);
            using (LinearGradientBrush brush = new LinearGradientBrush(rectRight, Color.Transparent, Color.Transparent, LinearGradientMode.Horizontal))
            {
                brush.InterpolationColors = blend2;
                g.FillRectangle(brush, rectRight);
            }
            Rectangle rectBottom = new Rectangle(rect.X, rect.Bottom, rect.Width, radius);
            using (LinearGradientBrush brush = new LinearGradientBrush(rectBottom, shadeColor, Color.Transparent, LinearGradientMode.Vertical))
            {
                brush.InterpolationColors = blend2;
                g.FillRectangle(brush, rectBottom);
            }
            using (GraphicsPath path = new GraphicsPath())
            {    // 左上
                Rectangle selRect = new Rectangle(rect.X - radius, rect.Y - radius, length, length);
                path.AddEllipse(selRect);
                using (PathGradientBrush pathBrush = new PathGradientBrush(path))
                {
                    pathBrush.CenterColor = Color.FromArgb(shadowAlpha, shadeColor);
                    pathBrush.CenterPoint = rect.Location;
                    pathBrush.SurroundColors = new Color[] { Color.Transparent };
                    pathBrush.InterpolationColors = blend1;
                    g.FillPie(pathBrush, selRect, 180, 90);
                    pathBrush.Dispose();
                }
                path.Dispose();
            }
            using (GraphicsPath path = new GraphicsPath())
            {    // 左下
                Rectangle selRect = new Rectangle(rect.X - radius, rect.Bottom - radius, length, length);
                path.AddEllipse(selRect);
                using (PathGradientBrush pathBrush = new PathGradientBrush(path))
                {
                    pathBrush.CenterColor = Color.FromArgb(shadowAlpha, shadeColor);
                    pathBrush.CenterPoint = new PointF(rect.X, rect.Bottom);
                    pathBrush.SurroundColors = new Color[] { Color.Transparent };
                    pathBrush.InterpolationColors = blend1;
                    g.FillPie(pathBrush, selRect, 90, 90);
                    pathBrush.Dispose();
                }
                path.Dispose();
            }
            using (GraphicsPath path = new GraphicsPath())
            {    // 右上
                Rectangle selRect = new Rectangle(rect.Right - radius, rect.Y - radius, length, length);
                path.AddEllipse(selRect);
                using (PathGradientBrush pathBrush = new PathGradientBrush(path))
                {
                    pathBrush.CenterColor = Color.FromArgb(shadowAlpha, shadeColor);
                    pathBrush.CenterPoint = new PointF(rect.Right, rect.Y);
                    pathBrush.SurroundColors = new Color[] { Color.Transparent };
                    pathBrush.InterpolationColors = blend1;
                    g.FillPie(pathBrush, selRect, 270, 90);
                    pathBrush.Dispose();
                }
                path.Dispose();
            }
            using (GraphicsPath path = new GraphicsPath())
            {    // 右下
                Rectangle selRect = new Rectangle(rect.Right - radius, rect.Bottom - radius, length, length);
                path.AddEllipse(selRect);
                using (PathGradientBrush pathBrush = new PathGradientBrush(path))
                {
                    pathBrush.CenterColor = Color.FromArgb(shadowAlpha, shadeColor);
                    pathBrush.CenterPoint = new PointF(rect.Right, rect.Bottom);
                    pathBrush.SurroundColors = new Color[] { Color.Transparent };
                    pathBrush.InterpolationColors = blend1;
                    g.FillPie(pathBrush, selRect, 0, 90);
                    pathBrush.Dispose();
                }
                path.Dispose();
            }
        }

        public void OnCheckChanged(TextItem obj)
        {
            if (CheckChanged != null)
            {
                CheckChanged(obj);
            }
        }

        void gdiForm_LostFocus(object sender, EventArgs e)
        {
            gdiForm.Hide();
        }

        void gdiForm_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (m_CurrentItem is TextItem)
                {
                    TextItem menuItem = m_CurrentItem as TextItem;
                    if (menuItem.Enabled && menuItem.Items.Count == 0)
                    {
                        if (ItemClicked != null)
                            ItemClicked(menuItem);
                        menuItem.OnClick();
                    }
                }
            }
        }

        void gdiForm_MouseMove(object sender, MouseEventArgs e)
        {
            GetCurrentItems(Items);
            foreach (BaseMenuItem item in m_HoverMenuItems)
            {
                if (item is TextItem)
                {
                    TextItem menuItem = item as TextItem;
                    menuItem.Selected = false;
                    if (menuItem.Bounds.Contains(Control.MousePosition))
                    {
                        menuItem.Selected = true;
                        m_CurrentItem = menuItem;
                        foreach (BaseMenuItem it in menuItem.Items)
                        {
                            it.Selected = false;
                        }
                    }
                }
            }
            gdiForm.Refresh();
        }

        //递归遍历菜单，找到鼠标当前所在菜单
        private void GetCurrentItems(MenuItemCollection items)
        {
            if (ExpandRect(items.Bounds, ShadowSize).Contains(Control.MousePosition))
            {
                m_HoverMenuItems = items;
            }
            foreach (BaseMenuItem item in items)
            {
                if (item is TextItem)
                {
                    TextItem it = item as TextItem;
                    if (it.Selected)
                        GetCurrentItems(it.Items);
                }
            }
        }

        public int GetItemsHeight(MenuItemCollection items)
        {
            int height = 0;
            foreach (BaseMenuItem item in items)
            {
                height += item is SplitItem ? SplitHeight : items.ItemHeight;
            }
            return height;
        }

        private Rectangle ExpandRect(Rectangle rect, int len)
        {
            return new Rectangle(rect.X - len, rect.Y - len, rect.Width + 2 * len, rect.Height + 2 * len);
        }

        public Point GetSatrtPos(TextItem dropItem)
        {
            MenuItemCollection items = dropItem.OwnerItems;
            Point pt = new Point(items.StartPos.X + items.ItemWidth, dropItem.Bounds.Y);
            Rectangle rect = Screen.AllScreens[0].WorkingArea;
            if (items.StartPos.X + items.ItemWidth + dropItem.Items.ItemWidth >= rect.Right)
                pt.X -= items.ItemWidth + dropItem.Items.ItemWidth;
            int dropItHeight = GetItemsHeight(dropItem.Items);
            if (dropItem.Bounds.Y + dropItHeight >= rect.Bottom)
                pt.Y = rect.Bottom - dropItHeight - 2;
            return pt;
        }

        /// <summary>
        /// 鼠标位置显示
        /// </summary>
        /// <param name="item"></param>
        public void Show(IWin32Window w)
        {
            var pt = new Point(Control.MousePosition.X + expendLength, Control.MousePosition.Y + expendLength);
            Show(w, pt);
        }

        /// <summary>
        /// 指定位置显示
        /// </summary>
        public void Show(IWin32Window w, Point pt)
        {
            var location = new Point(pt.X + expendLength, pt.Y + expendLength);
            SyncSize(location);
            CalcItemSize(Items);
            CalcItemPosition(Items);
            gdiForm.Refresh();
            gdiForm.Show(w);
        }

        /// <summary>
        /// 计算各级菜单位置
        /// </summary>
        private void CalcItemPosition(MenuItemCollection items)
        {
            int height = 0;
            items.Bounds = ExpandRect(new Rectangle(items.StartPos, new Size(items.ItemWidth, GetItemsHeight(items))), expendLength);
            foreach (BaseMenuItem item in items)
            {
                item.Bounds = new Rectangle(items.StartPos.X, items.StartPos.Y + height, items.ItemWidth, items.ItemHeight);
                height += item is SplitItem ? SplitHeight : items.ItemHeight;
                if (item is TextItem)
                {
                    TextItem menuItem = item as TextItem;
                    if (menuItem.Items.Count > 0)
                    {
                        menuItem.Items.StartPos = GetSatrtPos(menuItem);
                        CalcItemPosition(menuItem.Items);
                    }
                }
            }
        }

        /// <summary>
        /// 计算集合文本和快捷键最大宽度和高度
        /// </summary>
        /// <param name="font"></param>
        public void CalcItemSize(MenuItemCollection items)
        {
            int textWidth = 0;
            int quickKeyWidth = 0;
            int itemHeight = 0;
            foreach (BaseMenuItem item in items)
            {
                if (item is TextItem)
                {
                    TextItem menuItem = item as TextItem;
                    Size textSize = TextRenderer.MeasureText(menuItem.Text, Font);
                    int width = textSize.Width;
                    int height = textSize.Height;
                    if (height > itemHeight)
                        itemHeight = height;
                    if (width > textWidth)
                        textWidth = width;
                    width = TextRenderer.MeasureText(menuItem.QuickKey, Font).Width;
                    if (width > quickKeyWidth)
                        quickKeyWidth = width;
                    if (menuItem.Items.Count > 0)
                        CalcItemSize(menuItem.Items);
                }
            }
            items.ItemHeight = itemHeight + TextHeightSp;
            items.TextWidth = textWidth + TextWidthSp;
            items.QuickKeyWidth = quickKeyWidth + QuickKeyWidthSp;
        }

        private void SyncSize(Point mousePosition)
        {
            Items.StartPos = mousePosition;
            Rectangle rect = Screen.AllScreens[0].WorkingArea;
            if (mousePosition.X + Items.ItemWidth >= rect.Right)
                Items.StartPos.X -= Items.ItemWidth;
            int h = GetItemsHeight(Items);
            if (mousePosition.Y + h >= rect.Bottom)
                Items.StartPos.Y -= h;
            foreach (BaseMenuItem it in Items)
            {
                it.Selected = false;
            }
        }
 
        protected override void Dispose(bool disposing)
        {
            Items.Clear();
            base.Dispose(disposing);
        }
    }
}
